package com.cellulam.framework.scheduled;

import com.cellulam.framework.scheduled.exceptions.TaskDelayException;
import com.cellulam.framework.scheduled.exceptions.TaskExpireException;
import com.cellulam.framework.scheduled.exceptions.TaskTerminateException;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.lang.management.ManagementFactory;
import java.time.LocalDateTime;
import java.util.List;
import java.util.concurrent.DelayQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

@Slf4j
@Component
public abstract class AbstractTaskSchedule {

    private DelayQueue<DelayScheduleTask> delayQueue = new DelayQueue<>();

    private Config config;

    public static final String PROC_ID = ManagementFactory.getRuntimeMXBean().getName();

    private final ExecutorService TASK_POOL = createTaskPool();

    @PostConstruct
    private void postConstruct() {

        this.config = new Config();
        this.config.setFetchBatch(500);
        this.config.setFetchTtl(600);
        this.config.setTaskMaxCount(10);
        this.config.setTaskMaxDays(5);

        this.startDelayConsumer();
    }

    protected void setConfig(Config config) {
        this.config = config;
    }

    private void startDelayConsumer() {
        new Thread(() -> {
            while (true) {
                try {
                    DelayScheduleTask delayScheduleTask = this.delayQueue.take();
                    if (delayScheduleTask.getTask() != null) {
                        this.process(delayScheduleTask.getTask());
                    }
                } catch (Exception e) {
                    log.error("消费队列异常", e);
                }
            }
        }).start();
    }

    @Scheduled(cron = "0 0/5 * * * ?")
    public void taskExecute() {
        this.runTask();
    }

    protected void runTask() {
        log.debug("开始处理任务");


        List<ScheduleTask> tasks = this.fetch(PROC_ID, this.config.getFetchTtl(), this.config.getFetchBatch());

        if (tasks == null || tasks.isEmpty()) {
            return;
        }

        for (ScheduleTask task : tasks) {
            try {

                if (task.getScheduleStartTime() != null
                        && LocalDateTime.now().isBefore(task.getScheduleStartTime())) {
                    this.delayProcess(task);
                    continue;
                }

                this.TASK_POOL.submit(() -> {
                    try {
                        process(task);
                    } catch (Exception e) {
                        log.error("处理异常: {}", task.getId(), e);
                    }
                });

            } catch (Exception e) {
                log.error("", e);
            }
        }
    }

    private void delayProcess(ScheduleTask task) {
        try {
            this.delayQueue.offer(new DelayScheduleTask(task));
            log.debug("任务{}加入延迟队列", task.getScheduleStartTime());
        } catch (Exception e) {
            log.error("放入延迟队列异常", e);
        }
    }

    private void process(ScheduleTask task) {
        if (this.tryProcess(task.getId(), this.config.getFetchTtl())) {
            TaskProcessor taskProcessor = null;
            try {
                taskProcessor = TaskProcessorHolder.holder.getProcessor(task.getType(),
                        task.getCommand(),
                        task.getVersion());

                log.debug("开始处理, 任务ID: {}, id: {}, type: {}, command: {}, version: {}",
                        task.getId(),
                        task.getType(),
                        task.getCommand(),
                        task.getVersion());

                taskProcessor.processBegin(task);
                ExecuteResult result = taskProcessor.run(task);
                taskProcessor.processComplete(task, result);
                this.processComplete(task, result);
            } catch (TaskExpireException e) {
                log.error("执行任务[{}]过期，终止任务", task.getId(), e);
                if (taskProcessor != null) {
                    taskProcessor.processExpire(task);
                }
                this.processComplete(task, new ExecuteResult(TaskExeResult.EXPIRED, e.getMessage()));
            } catch (TaskTerminateException e) {
                log.error("执行任务[{}]失败，终止任务", task.getId(), e);
                ExecuteResult result = new ExecuteResult(false, e.getMessage());
                if (taskProcessor != null) {
                    taskProcessor.processComplete(task, result);
                }
                this.processComplete(task, result);
            } catch (TaskDelayException e) {
                log.debug("任务[{}]延迟处理: {}", task.getId(), e.getMessage());
                this.delayTask(task.getId(), 0, e.getMessage());
            } catch (Exception e) {
                log.error("任务[{}]执行异常: ", task.getId(), e);
                if (task.getExeCount() > this.config.getTaskMaxCount()
                        && task.getCreateTime()
                        .plusDays(this.config.getTaskMaxDays())
                        .isBefore(LocalDateTime.now())) {

                    ExecuteResult result = new ExecuteResult(false,
                            e.toString() + e.getMessage());
                    if (taskProcessor != null) {
                        taskProcessor.processException(task, e);
                    }
                    this.processComplete(task, result);
                } else {
                    this.delayTask(task.getId(), 1, e.getMessage());
                }
            }
        }
    }

    protected ExecutorService createTaskPool() {
        return Executors.newSingleThreadExecutor();
    }

    /**
     * 延迟执行
     *
     * @param taskId 任务ID
     * @param count  累加执行次数
     * @param memo   消息
     */
    protected abstract void delayTask(long taskId, int count, String memo);

    /**
     * 任务抓取
     *
     * @param procId
     * @param ttl
     * @param limit
     * @return
     */
    protected abstract List<ScheduleTask> fetch(String procId, int ttl, int limit);

    /**
     * 尝试处理
     *
     * @param taskId
     * @param ttl
     * @return
     */
    protected abstract boolean tryProcess(long taskId, int ttl);

    /**
     * 处理完成
     *
     * @param task
     * @param result
     */
    protected abstract void processComplete(ScheduleTask task, ExecuteResult result);

    @Data
    public static class Config {
        /**
         * 抓取数据ttl
         */
        private int fetchTtl;

        /**
         * 每次抓取批次
         */
        private int fetchBatch;

        /**
         * 每个任务最大执行次数
         */
        private int taskMaxCount;

        /**
         * 每个任务最多执行多少天
         */
        private int taskMaxDays;
    }
}
